/**
 * 抖音关注：程序员三丙
 * 知识星球：https://t.zsxq.com/j9b21
 */
package sanbing.example.dylike.infrastructure.redis;

import io.lettuce.core.api.StatefulRedisConnection;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

@Configuration
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Profile("!test")
@Data
@Slf4j
public abstract class SanbingRedisCacheConfiguration {

    private static final String COMMA = ",";
    private static final String COLON = ":";

    @Value("${redis.pool_config.maxTotal:128}")
    private int maxTotal;

    @Value("${redis.pool_config.maxIdle:128}")
    private int maxIdle;

    @Value("${redis.pool_config.minIdle:16}")
    private int minIdle;

    @Value("${redis.pool_config.testOnBorrow:true}")
    private boolean testOnBorrow;

    @Value("${redis.pool_config.testOnReturn:true}")
    private boolean testOnReturn;

    @Value("${redis.pool_config.testWhileIdle:true}")
    private boolean testWhileIdle;

    @Value("${redis.pool_config.minEvictableMs:60000}")
    private long minEvictableMs;

    @Value("${redis.pool_config.evictionRunsMs:30000}")
    private long evictionRunsMs;

    @Value("${redis.pool_config.maxWaitMills:60000}")
    private long maxWaitMills;

    @Value("${redis.pool_config.numberTestsPerEvictionRun:3}")
    private int numberTestsPerEvictionRun;

    @Value("${redis.pool_config.blockWhenExhausted:true}")
    private boolean blockWhenExhausted;

    @Value("${redis.ssl.enabled:false}")
    private boolean sslEnabled;

    @Bean
    public ReactiveRedisConnectionFactory redisConnectionFactory() {
        return loadFactory();
    }

    protected abstract LettuceConnectionFactory loadFactory();

    @Bean
    public ReactiveRedisTemplate<UUID, Integer> reactiveRedisTemplate(ReactiveRedisConnectionFactory redisConnectionFactory) {

        return new ReactiveRedisTemplate<>(redisConnectionFactory, RedisSerializationContext
                .<UUID, Integer>newSerializationContext(new RedisSerializer<UUID>() {
                    @Override
                    public byte[] serialize(UUID key) throws SerializationException {
                        return key == null ? null : key.toString().getBytes(StandardCharsets.UTF_8);
                    }

                    @Override
                    public UUID deserialize(byte[] bytes) throws SerializationException {
                        return bytes == null ? null : UUID.fromString(new String(bytes, StandardCharsets.UTF_8));
                    }
                })
                .value(new RedisSerializer<>() {
                    @Override
                    public byte[] serialize(Integer value) throws SerializationException {
                        return value.toString().getBytes();
                    }

                    @Override
                    public Integer deserialize(byte[] bytes) throws SerializationException {
                        return Integer.valueOf(new String(bytes, StandardCharsets.UTF_8));
                    }
                })
                .build());
    }

    protected GenericObjectPoolConfig<StatefulRedisConnection<String, String>> buildPoolConfig() {
        GenericObjectPoolConfig<StatefulRedisConnection<String, String>> poolConfig = new GenericObjectPoolConfig<>();
        poolConfig.setMaxTotal(maxTotal);
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMinIdle(minIdle);
        poolConfig.setTestOnBorrow(testOnBorrow);
        poolConfig.setTestOnReturn(testOnReturn);
        poolConfig.setTestWhileIdle(testWhileIdle);
        poolConfig.setSoftMinEvictableIdleDuration(Duration.ofMillis(minEvictableMs));
        poolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(evictionRunsMs));
        poolConfig.setMaxWait(Duration.ofMillis(maxWaitMills));
        poolConfig.setNumTestsPerEvictionRun(numberTestsPerEvictionRun);
        poolConfig.setBlockWhenExhausted(blockWhenExhausted);
        return poolConfig;
    }

    protected List<RedisNode> getNodes(String nodes) {
        List<RedisNode> result;
        if (StringUtils.isBlank(nodes)) {
            result = Collections.emptyList();
        } else {
            result = new ArrayList<>();
            for (String hostPort : nodes.split(COMMA)) {
                String host = hostPort.split(COLON)[0];
                int port = Integer.parseInt(hostPort.split(COLON)[1]);
                result.add(new RedisNode(host, port));
            }
        }
        return result;
    }


}
